<?php
/**
 * Упрощает работу с одной таблицей.
 * TODO отрефакторить класс с использованием метода array_shift
 * @since 16.05.2011
 * @author Akulov Denis <akulov.d.g@gmail.com>
 */
class DB_Reader{

    /**
     * Ссылка на объект для работы с базой данных.
     * @var DbSimple_Generic_Database
     */
    private $_obDB;


    /**
     * Имя таблице или представления (в терминологи класса - отношение, или
     * источник данных)
     * @var string
     */
    private $_strSource;


    /**
     * Имя primary_key поля.
     * @var string
     */
    private $_strID;


    /**
     * @param DbSimple_Generic $obDB
     * @param string $strSourceName
     * @param string $strKey
     */
    public function __construct (DbSimple_Generic_Database $obDB, $strSourceName, $strKeyName) {
        $this->_obDB = $obDB;
        $this->_strSource = $strSourceName;
        $this->_strID = $strKeyName;
    }


    /**
     * @return string
     */
    public function getSource () {
        return $this->_strSource;
    }


    /**
     * @return string
     */
    public function getKey () {
        return $this->_strID;
    }


    /**
     * @return DbSimple_Generic_Database
     */
    public function getDb () {
        return $this->_obDB;
    }


    /**
     * Возвращает элемент по идентификатору
     * @param mixed $id
     * @return array[]
     */
    public function element ($id) {
        return $this->_query(
            'selectRow',
            'SELECT * FROM ?# WHERE ?# = ?',
             array(
                 $this->getSource(),
                 $this->getKey(),
                 $id
             )
         );
    }


    /**
     * Возвращает значение по условию <br/>
     * Параметры: <b>string</b> <i>$minisql</i> [, <b>mixed</b> <i>$var1</i>] [, <b>mixed</b> <i>$var2</i>] [, <b>mixed</b> <i>$_</i>]
     * @return mixed
     */
    public function elementBy () {
        $arArgs = func_get_args();
        if (empty($arArgs)) {
            throw new Exception('Not found minisql query');
        }
        $strMinisql = $this->_msql($arArgs[0]);
        unset($arArgs[0]);

        return call_user_func_array(
            array(
                $this->getDb(),
                'selectRow'
             ),
            array_merge(
                array(
                   'SELECT * FROM ?# '.$strMinisql.' LIMIT 1',
                    $this->getSource()
                ),
                $arArgs
            )
        );
    }


    /**
     * Возвращает список элеметов по списку id, метод можно использовать 2-мя способами: <br/>
     * Параметры: <b>array</b> <i>$arElements</i><br/>
     * Параметры: <b>mixed</b> <i>$var1</i> [,<b>mixed</b> <i>$var2</i>] [,<b>mixed</b> <i>$_</i>]
     * @return array[][]
     */
    public function elements () {
        $args = func_get_args();
        if (is_array($args[0])) {
            $args = $args[0];
        }
        return $this->_query(
            'select',
            'SELECT * FROM ?# WHERE ?# IN (?a)',
            array(
                $this->getSource(),
                $this->getKey(),
                array_values($args)
            )
        );
    }


    /**
     * Возвращает все элементы <br/>
     * Параметры: <b>string</b> <i>$minisql</i> [, <b>mixed</b> <i>$var1</i>] [, <b>mixed</b> <i>$_</i>]
     */
    public function msql () {
        $args = func_get_args();
        $minisql = $args[0];
        $args[0] = $this->getSource();
        $sql = 'SELECT * FROM ?# '.$this->_msql($minisql);
        return $this->_query('select', $sql, $args);
    }


    /**
     * Возвращает первые N - записей <br/>
     * Параметры: <b>integer</b> <i>$limit</i>, <b>string</b> <i>$minisql</i> [,<b>mixed</b> <i>$var1</i>] [,<b>mixed</b> <i>$_</i>]
     */
    public function limit () {
        $args = func_get_args();
        $limit = $args[0]; unset($args[0]);
        $minisql = $args[1];unset($args[1]);
        $sql = 'SELECT * FROM ?# '.$this->_msql($minisql).' LIMIT ?d';
        $args = array_values(array_merge(array($this->getSource()),$args, array($limit)));
        return $this->_query(
            'select',
            $sql,
            $args
        );
    }


    /**
     * Возвращает количество элементов в таблице<br/>
     * Параметры: <b>string</b> <i>$where</i> [,<b>mixed</b> <i>$var1</i>] [,<b>mixed</b> <i>$_</i>]
     */
    public function count () {
        $args = func_get_args();
        $where = array_shift($args);
        $sql = 'SELECT COUNT(*) FROM ?# '.$this->_msql($where);
        $args = array_merge(array($this->getSource()), $args);
        return $this->_query(
            'selectCell',
            $sql,
            $args
        );
    }


    /**
     * Количество страниц, при условии что на страницe $count элементов <br/>
     * Параметры: <b>integer</b> <i>$count</i>, <b>string</b> <i>$where</i> [,<b>mixed</b> <i>$var1</i>] [,<b>mixed</b> <i>$_</i>]
     */
    public function numberOfPages () {
        $args = func_get_args();
        $count = array_shift($args);
        $where = array_shift($args);
        $sql = 'SELECT CEIL(COUNT(*)/?) FROM ?# '.$this->_msql($where);
        $args = array_merge(array($count, $this->getSource()), $args);
        return $this->_query(
            'selectCell',
            $sql,
            $args
        );
    }


    /**
     * Вывод страницы <br/>
     * Параметры: <b>integer</b> <i>&$page</i>, <b>integer</b> <i>$count</i>, <b>string</b> <i>$minisql</i> [,<b>mixed</b> <i>$var1</i>] [,<b>mixed</b> <i>$_</i>]
     */
    public function page (&$iPageNum, $iCount, $strMiniSql = '') {
        $arArgs = func_get_args();

        unset($arArgs[0]);
        unset($arArgs[1]);
        unset($arArgs[2]);

        $strSql = 'SELECT * FROM ?# '.$this->_msql($strMiniSql).' LIMIT ?d, ?d';

        $iMaxPage = call_user_func_array(
            array($this, 'numberOfPages'),
            array_merge(array($iCount, $this->_getWhere($strMiniSql)), $arArgs)
        );

        $iPageNum = $iPageNum < 1 ? 1 : $iPageNum;
        $iPageNum = $iMaxPage < $iPageNum ? $iMaxPage : $iPageNum;

        $iLeft  = ($iPageNum - 1) * $iCount;

        return $this->_query(
            'select',
            $strSql,
            array_merge(array($this->getSource()), $arArgs, array($iLeft, $iCount))
        );
    }


    /**
     * Проверяет существует ли строка по первичному ключу <br/>
     * Параметры: <b>mixed</b> <i>$id</i>
     * @return boolean
     */
    public function isExists ($id) {
        $count = $this->getDb()->selectCell(
            'SELECT count(*) FROM ?# WHERE ?# = ?',
            $this->getSource(),
            $this->getKey(),
            $id
        );

        if(!empty($count)){
            return true;
        }

        return false;
    }


    /**
     * Проверяет, существует ли строка в таблице удовлетворяющая заданному условию <br/>
     * Параметры: <b>string</b> <i>$where</i> [,<b>mixed</b> <i>$var1</i>] [,<b>mixed</b> <i>$_</i>]
     * @return boolean
     */
    public function isExistsByWhere () {
        $args = func_get_args();
        $iResult = call_user_func_array(
            array($this, 'count'),
            $args
        );

        if(!empty($iResult)){
            return true;
        }

        return false;
    }


    /**
     * Возвращает значение по идентификатору <br/>
     * Параметры: <b>mixed</b> <i>$id</i>, <b>string</b> <i>$field</i>
     * @return mixed
     */
    public function value ($id, $field) {
        return $this->getDb()->selectCell(
            'SELECT ?# FROM ?# WHERE ?# = ? LIMIT 1',
            $field,
            $this->getSource(),
            $this->getKey(),
            $id
        );
    }


    /**
     * Возвращает значение по условию <br/>
     * Параметры: <b>string</b> <i>$fieldName</i>, <b>string</b> <i>$minisql</i> [, <b>mixed</b> <i>$var1</i>] [, <b>mixed</b> <i>$var2</i>] [, <b>mixed</b> <i>$_</i>]
     * @return mixed
     */
    public function valueBy () {
        $arArgs = func_get_args();
        $strFieldName = $arArgs[0];
        unset($arArgs[0]);
        $strMinisql = $this->_msql($arArgs[1]);
        unset($arArgs[1]);

        return call_user_func_array(
            array(
                $this->getDb(),
                'selectCell'
             ),
            array_merge(
                array(
                   'SELECT ?# FROM ?# '.$strMinisql.' LIMIT 1',
                    $strFieldName,
                    $this->getSource()
                ),
                $arArgs
            )
        );
    }


    /**
     * Возвращает колонку по идентификатору (идентификатор возвращается как ключ массива)<br/>
     * Параметры: <b>string</b> <i>$field</i>, <b>integer</b> <i>$limit = 0</i> [, <b>string</b> <i>$minisql</i>] [, <b>mixed</b> <i>$var1</i>] [, <b>mixed</b> <i>$_</i>]
     * @return array[]
     */
    public function column () {
        $arArgs = func_get_args();
        $strFieldName = $arArgs[0];
        unset($arArgs[0]);
        $iLimit = $arArgs[1];
        if (empty($iLimit)) {
            $iLimit = DBSIMPLE_SKIP;
        }
        unset($arArgs[1]);
        $strMiniSql = $this->_msql($arArgs[2]);
        unset($arArgs[2]);
        return call_user_func_array(
            array($this->getDb(), 'selectCol'),
            array_merge(
                array(
                    'SELECT ?#, ?# as ARRAY_KEY FROM ?# '.$strMiniSql.'{ LIMIT ?d}',
                    $strFieldName,
                    $this->getKey(),
                    $this->getSource(),
                ),
                $arArgs,
                array($iLimit)
            )
        );
    }


    /**
     * Возвращает колонки по идентификатору (идентификатор возвращается как ключ массива)<br/>
     * Параметры: <b>array</b> <i>$fields</i> [, <b>string</b> <i>$minisql</i>] [, <b>mixed</b> <i>$var1</i>] [, <b>mixed</b> $_]
     * @return array[]
     */
    public function columns () {
        $arArgs = func_get_args();
        $arFields = $arArgs[0];
        unset($arArgs[0]);
        $iLimit = $arArgs[1];
        if (empty($iLimit)) {
            $iLimit = DBSIMPLE_SKIP;
        }
        unset($arArgs[1]);
        $strMinisql = $this->_msql($arArgs[2]);
        unset($arArgs[2]);
        $count = count($arFields);
        $strFieldsContainer = '?# as ARRAY_KEY';
        for ($i = 0; $i < $count; $i++) {
            $strFieldsContainer .= ', ?#';
        }
        return call_user_func_array(
            array($this->_obDB, 'select'),
            array_merge(
                array(
                    'SELECT '.$strFieldsContainer.' FROM ?# '.$strMinisql.'{ LIMIT ?d}'
                ),
                array($this->getKey()),
                $arFields,
                array(
                    $this->getSource(),
                ),
                $arArgs,
                array($iLimit)
            )
        );
    }



    private $_arFilterTypes = array(
        'equal'     => '{AND ?# = ?} ',
        'more'      => '{AND ?# > ?} ',
        'less'      => '{AND ?# < ?} ',
        'not equal' => '{AND ?# != ?} ',
        'between'   => '{AND ?# BETWEEN ? AND ?} ',
        'like'      => '{AND ?# LIKE ?} '
    );

    /**
     * Фильтр: array('поле таблицы' => 'шаблон поиска|значение[|значение]')
     * @param integer &$iPageNum
     * @param integer $iCount
     * @param array $arOrderBy
     * @param array $arFilter
     */
    public function pageByFilterArray (&$iPageNum, $iCount, $strOrderField = '', $arFilter = array()) {
        $strSubSqlFilter = '1 ';
        $arFilterArgs = array();
        $this->_prepareFilterArray($arFilter, $strSubSqlFilter, $arFilterArgs);

        if (empty($strOrderField)) {
            $strOrderField = DBSIMPLE_SKIP;
        }

        $strSql = 'SELECT * FROM ?# WHERE '.$strSubSqlFilter.
                  '{ORDER BY ?# ASC} LIMIT ?d, ?d';

        $iMaxPage = call_user_func_array(
            array($this, 'numberOfPages'),
            array_merge(array($iCount, $strSubSqlFilter), $arFilterArgs)
        );

        $iPageNum = $iPageNum < 1 ? 1 : $iPageNum;
        $iPageNum = $iMaxPage < $iPageNum ? $iMaxPage : $iPageNum;

        $iLeft  = ($iPageNum - 1) * $iCount;

        return $this->_query(
            'select',
            $strSql,
            array_merge(array($this->getSource()), $arFilterArgs, array($strOrderField), array($iLeft, $iCount))
        );
    }


    public function numberOfPagesByFilterArray ($iNumberOfElementsInPage, $arFilter = array()) {
        $strSubSqlFilter = '1 ';
        $arFilterArgs = array();
        $this->_prepareFilterArray($arFilter, $strSubSqlFilter, $arFilterArgs);

        return call_user_func_array(
            array($this, 'numberOfPages'),
            array_merge(array($iNumberOfElementsInPage, $strSubSqlFilter), $arFilterArgs)
        );
    }


    private function _msql ($minisql) {
        if(empty($minisql)) {
            return '';
        }

        if( substr_count($minisql, '|') < 1) {
            $minisql .= '|';
        }

        list($where, $orderby) = explode('|', $minisql);

        if (empty($where) && empty($orderby)) {
            return '';
        }

        if(empty($orderby)) {
            return 'WHERE '.$where;
        }

        if(empty($where)) {
            return 'ORDER BY '.$orderby;
        }

        return 'WHERE '.$where.' ORDER BY '.$orderby;
    }


    private function _getWhere ($minisql) {
        if (substr_count($minisql, '|') < 1) {
            return $minisql;
        }

        $arResult = explode('|', $minisql);

        return $arResult[0];
    }


    /**
     * Параметры: <b>string</b> <i>$method</i>, <b>string</b> <i>$query</i>, <b>array</b> <i>$args(var1, var2, var3, _)</i>
     */
    private function _query () {
        $args = func_get_args();
        $method = $args[0];
        $args = array_merge(array($args[1]),$args[2]);
        return call_user_func_array(
            array($this->getDb(), $method),
            $args
        );
    }


    private function _prepareFilterArray($arFilter, &$strSubSqlFilter, &$arFilterArgs) {
        foreach ($arFilter as $field => $option) {
            $arOption = explode('|', $option);

            if (key_exists($arOption[0], $this->_arFilterTypes)) {
                $strTemplateFilter = $this->_arFilterTypes[$arOption[0]];
            }

            if (empty($strTemplateFilter)) {
                throw new Exception('Filter type '.$arOption[0].'not found');
            }

            unset($arOption[0]);

            $strSubSqlFilter .= $strTemplateFilter;
            $arFilterArgs[]   = $field;
            $arFilterArgs     = array_merge($arFilterArgs, array_values($arOption));
        }

    }

}